home *** CD-ROM | disk | FTP | other *** search
/ ETO Development Tools 1 / ETO Development Tools 1.iso / Essentials / Developer Essentials Jul 90 / DTS Sample Code / Macintosh Sample Code / SC.024.SoundApp / SoundUnit.p < prev   
Encoding:
Text File  |  1990-05-01  |  46.9 KB  |  1,318 lines  |  [TEXT/MPS ]

  1. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. #
  3. # Apple Macintosh Developer Technical Support
  4. #
  5. # SoundUnit
  6. #
  7. # SoundUnit.p    - MPW 3.0/3.1 Pascal Source
  8. #
  9. # Versions:
  10. #             1.03                    May, 1990
  11. #
  12. # Components:
  13. #             SoundApp.make        May 1, 1990            MPW build script
  14. #             SoundApp.p            May 1, 1990            Pascal source code
  15. #             SoundApp.r            May 1, 1990            Rez source code
  16. #             SoundAppSnds.r        May 1, 1990            Rez source code
  17. #             SoundUnit.p            May 1, 1990            Pascal source code
  18. #
  19. # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
  20. #
  21. # SoundApp.p is a sample application source file for demonstrating the
  22. # Sound Manager.  This portion of the source code handles the Sound Manager
  23. # part of the application.  This UNIT can be used by others.
  24. #
  25. # Jim Reekes E.O., Macintosh Developer Technical Support
  26. # Tuesday, January 30, 1990  1:01 PM
  27. #
  28. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  29.  
  30. UNIT SoundUnit;
  31.  
  32. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  33. INTERFACE
  34. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  35.  
  36. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  37. USES
  38. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  39. {Only include the interface files that I really need.  This helps MPW
  40.  to compile faster.}
  41.  
  42.     Types, Memory, Resources, OSUtils, Errors, FixMath, SANE, Sound;
  43.  
  44. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  45. CONST
  46. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  47.  
  48. {refer to the SoundAppSnds.r file for documentation on these note values}
  49.     kOctave1 =            0;                    {octaves of MIDI note values}
  50.     kOctave2 =            12;
  51.     kOctave3 =            24;
  52.     kOctave4 =            36;
  53.     kOctave5 =            48;
  54.     kOctave6 =            60;
  55.     kOctave7 =            72;
  56.     kOctave8 =            84;
  57.     kOctave9 =            96;
  58.     kOctave10 =            108;
  59.     kOctave11 =            120;
  60.  
  61.     Akey =                -3;                {the note A}
  62.     Bbkey =                -2;                {the note B flat}
  63.     Bkey =                -1;                {the note B}
  64.     Ckey =                0;                    {the note C}
  65.     Dbkey =                1;                    {the note D flat}
  66.     Dkey =                2;                    {the note D}
  67.     Ebkey =                3;                    {the note D flat}
  68.     Ekey =                4;                    {the note E}
  69.     Fkey =                5;                    {the note F}
  70.     Gbkey =                6;                    {the note G flat}
  71.     Gkey =                7;                    {the note G}
  72.     Abkey =                8;                    {the note A flat}
  73.  
  74. {These are other constants used in the SoundUnit}
  75.     kInitNone =            0;                    {no init options}
  76.     kWait =                FALSE;            {wait for the channel}
  77.     kSMAsynch =            TRUE;                {asynchronous Sound Manager call}
  78.     kMiddleC =            kOctave6 + Ckey; {MIDI note value of middle C}
  79.     kWaveSize =            512;                {standard size of wave table}
  80.     kSyncID =            $12345678;        {identifier used in syncCmd}
  81.     kOneSecond =        2000;                {one second note duration}
  82.     kFullAmp =            $FF000000;        {full amplitude setting}
  83.  
  84.  
  85. {These determine the type of sound header is in a sampled sound.
  86.  The most common is standard.  MACE using compressed. Comment these out
  87.  for MPW 3.2 and later.}
  88.     stdSH =                $00;                {standard sound header}
  89.     extSH =                $FF;                {extended sound header}
  90.     cmpSH =                $FE;                {compressed sound header}
  91.  
  92. {These are used as flags in the sound channel to determine the state
  93.  of that channel.  The 'snth' IDs are used when the channel is in use
  94.  to determine what that channel in intended for.}
  95.     kNoSynth =            0;                    {no synth is specified}
  96.     kChanComplete =    -1;                {channel has completed}
  97.     kChanFree =            MAXLONGINT;        {channel is not in use}
  98.     kSoundComplete =    $1234;            {flag for callBackCmd}
  99.  
  100. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  101. TYPE
  102. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  103.  
  104.     SndCmdPtr =            ^SndCommand;    {Ptr to a sound command, for type coersion}
  105.     IntPtr =                ^INTEGER;        {Ptr to a INTEGER, for type coersion}
  106.  
  107. {I have declared a few TYPEs below to help examine 'snd ' resources.
  108.  I have to break them up into individual pieces because they are
  109.  variable sized records.}
  110.  
  111.     Snd1Header =        RECORD
  112.         format:            INTEGER;
  113.         numSynths:        INTEGER;
  114.     END;
  115.     Snd1HdrPtr =        ^Snd1Header;
  116.     Snd1HdrHndl =        ^Snd1HdrPtr;
  117.  
  118.     SynthInfo =            RECORD
  119.         synthID:            INTEGER;
  120.         initOption:        LONGINT;
  121.     END;
  122.     SynthInfoPtr =        ^SynthInfo;
  123.  
  124.     Snd2Header =        RECORD
  125.         format:            INTEGER;
  126.         refCount:        INTEGER;
  127.     END;
  128.     Snd2HdrPtr =        ^Snd2Header;
  129.     Snd2HdrHndl =        ^Snd2HdrPtr;
  130.  
  131. {I have created my own sound channel type.  It extends the normal
  132.  sound channel by adding a few fields.  To confirm that the channel
  133.  in question is mine, I set the userInfo field to something that I
  134.  can recognize.  I keep the 'snd ' resource handle associated to this
  135.  channel too.  This allows me to dispose of the data once the channel
  136.  has completed its duties.}
  137.  
  138.     MyChanType =        RECORD                {this is a 1064 byte structure}
  139.         theChan:            SndChannel;            {must keep sound channel as first field}
  140.         dataHandle:        Handle;
  141.     END;
  142.     MyChanPtr =            ^MyChanType;
  143.  
  144. {I’ve had to re-define the SoundHeader to correctly match the current version.
  145.  This is correctly defined in MPW 3.2 or later.}
  146.  
  147.     MySndHeader =         PACKED RECORD
  148.         samplePtr:         Ptr;                     {if NIL then samples are in sampleArea}
  149.         length:             LONGINT;
  150.         sampleRate:     Fixed;
  151.         loopStart:        LONGINT;
  152.         loopEnd:         LONGINT;
  153.         encode:             BYTE;                    {special header type field}
  154.         baseNote:         BYTE;                 {base note now byte}
  155.         sampleArea:     PACKED ARRAY [0..0] OF Byte;
  156.         END;
  157.     MySndHeaderPtr = ^MySndHeader;
  158.  
  159. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  160. VAR {The “g” prefix is used to emphasize that a variable is global.}
  161. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  162.  
  163. {These four global pointers are to my sound channels.  One of them has to
  164.  be global and I could utilize the fact that they are a linked list.  That
  165.  would complicate things to some degree, and it really wouldn’t have that
  166.  much of an advantage.}
  167.  
  168.     gChan1:             MyChanPtr;
  169.     gChan2:             MyChanPtr;
  170.     gChan3:             MyChanPtr;
  171.     gChan4:             MyChanPtr;
  172.  
  173. {This is the global flag that is set once the sound channel’s call back
  174.  procedure has been called.  This, in my case, means the channel has
  175.  completed its duties and is time for disposing.}
  176.  
  177.     gCalledBack:    BOOLEAN;
  178.  
  179. {gChanOpen is a flag set to determine if the application has a sound
  180.  channel open.  It’s really not feasible to determine if a sound is being
  181.  made at any given point.}
  182.  
  183.      gChanOpen:        BOOLEAN;
  184.  
  185.  
  186. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  187.  
  188. {The routines below are for public consumption in this UNIT.  Note that
  189.  these all return standard sound channels to allow easy of modifications.
  190.  I can change the structure of my own sound channels and the code without
  191.  forcing a change in the application that uses this UNIT.}
  192.  
  193. PROCEDURE _SANELib;
  194. PROCEDURE _SoundUnit;
  195.  
  196. FUNCTION InitSoundUnit: OSErr;
  197. FUNCTION SoundCompletion: BOOLEAN;
  198. FUNCTION SndChanOpen: BOOLEAN;
  199. FUNCTION SetNoteTimbre(noteChan: SndChannelPtr; timbre: INTEGER;
  200.                                 immediate: BOOLEAN): OSErr;
  201. FUNCTION SendNote(chan: SndChannelPtr; duration: INTEGER; note: LONGINT): OSErr;
  202. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  203. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  204. PROCEDURE DoSoundComplete;
  205. PROCEDURE FreeAllChans;
  206. PROCEDURE FreeSoundUnit;
  207. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  208. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  209. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  210. FUNCTION GetSndDataOffset(sndHandle: Handle;
  211.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  212. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  213.                                                         sndInstrument: Handle): OSErr;
  214. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  215.                             waveLength: INTEGER): OSErr;
  216. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  217.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  218. FUNCTION GetNoteChan(VAR noteChan: SndChannelPtr; timbre: INTEGER): OSErr;
  219. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  220. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  221. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  222. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  223.                             song1, song2, song3, song4: Handle): OSErr;
  224. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  225. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  226.  
  227.  
  228. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  229. IMPLEMENTATION
  230. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  231. {$S SANELib}
  232. PROCEDURE _SANELib;
  233.  
  234. {This is a dummy routine to allow the application to unload the SANE library
  235.  that this UNIT uses.}
  236.  
  237. BEGIN
  238. END;
  239.  
  240. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  241. {$S SoundUnit}
  242. PROCEDURE _SoundUnit;
  243.  
  244. {This is a dummy routine to allow the application to unload this UNIT.}
  245.  
  246. BEGIN
  247. END;
  248.  
  249. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  250. {$S Initialize}
  251. FUNCTION InitSoundUnit: OSErr;
  252.  
  253. {Create storage for each of the four channels (4 * 1064 bytes) and
  254.  initialize them.  If any of the channels cannot be allocated, return an
  255.  error.  Also initialized our global flag “gCalledBack.” An interesting
  256.  modification would be to allow the caller to pass in the number of
  257.  channels the application really intends on using. If the user only wants
  258.  one channel, then we could just allocate one instead of four.  These
  259.  channels are used at interrupt time.}
  260.  
  261.     FUNCTION InitMyChan(VAR newChan: MyChanPtr): BOOLEAN;
  262.     BEGIN
  263.         newChan:= MyChanPtr(NewPtrClear(SizeOf(MyChanType)));
  264.         IF newChan <> NIL THEN BEGIN
  265.             newChan^.theChan.qLength:= stdQLength;
  266.             newChan^.theChan.userInfo:= kChanFree;
  267.             InitMyChan:= TRUE;
  268.         END ELSE
  269.             InitMyChan:= FALSE;
  270.     END;
  271.  
  272. BEGIN
  273.     gChanOpen:= FALSE;
  274.     gCalledBack:= FALSE;
  275.     IF InitMyChan(gChan1) THEN
  276.         IF InitMyChan(gChan2) THEN
  277.             IF InitMyChan(gChan3) THEN
  278.                 IF InitMyChan(gChan4) THEN;
  279.     InitSoundUnit:= MemError;
  280. END;
  281.  
  282. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  283. {$S Main}
  284. FUNCTION SoundCompletion: BOOLEAN;
  285.  
  286. {This routine can be called to determine if the sound has completed.  When
  287.  this is true, the sound data can be disposed of.  The global “gCalledBack”
  288.  determines whether the sound has completed or not and it is set by the
  289.  sound channel’s completion routine.  Soundcompletion is placed in the Main
  290.  segment because it is called by the event loop.}
  291.  
  292. BEGIN
  293.     SoundCompletion:= gCalledBack;
  294. END;
  295.  
  296. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  297. {$S Main}
  298. FUNCTION SndChanOpen: BOOLEAN;
  299.  
  300. {This routine can be called at any time.  It will return TRUE when the
  301.  SoundUnit has an open channel.  This can be can considered the same as
  302.  sound being active.  As long as the channel is open, no other channels can
  303.  be opened.  It is placed in the Main segment because it is called by the
  304.  event loop.}
  305.  
  306. BEGIN
  307.     SndChanOpen:= gChanOpen;
  308. END;
  309.  
  310. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  311. {$S SoundUnit}
  312. FUNCTION SetNoteTimbre(noteChan: SndChannelPtr; timbre: INTEGER;
  313.                                 immediate: BOOLEAN): OSErr;
  314.  
  315. {Given a channel and timbre (sounds like “tom burr”), this will adjust the
  316.  tone quality of the note synthesizer.  Changing the tone can only be done
  317.  before playing a note.  On a Mac with the Apple Sound Chip, this can be
  318.  done in real time while a note is playing.  But, since there’s no
  319.  supported method for determining if the ASC is available I have to assume
  320.  that it’s not.  I use the immediate flag to determine if the user wants to
  321.  change the timbre now, or queue the command.  If the queue is full, it
  322.  will wait for the command to be accepted.
  323.  
  324.  BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  325.  SE where sending a timbreCmd with a timbre of 255 (a legal value) will
  326.  crash.  The difference between 254 and 255 isn’t audible, so I only allow
  327.  a maximum of 254 in any case.}
  328.  
  329. VAR
  330.     theCmd:        SndCommand;
  331.  
  332. BEGIN
  333.     IF timbre > 254 THEN
  334.         timbre:= 254;
  335.     WITH theCmd DO BEGIN
  336.         cmd:= timbreCmd;
  337.         param1:= timbre;
  338.         param2:= 0;
  339.     END;
  340.     IF immediate THEN
  341.         SetNoteTimbre:= SndDoImmediate(noteChan, theCmd)
  342.     ELSE
  343.         SetNoteTimbre:= SndDoCommand(noteChan, theCmd, kWait);
  344. END;
  345.  
  346. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  347. {$S SoundUnit}
  348. FUNCTION SendNote(chan: SndChannelPtr; duration: INTEGER; note: LONGINT): OSErr;
  349.  
  350. {Given a channel and note information, this will place the note into the
  351.  channel’s queue.  The note contains the amplitude and note value.  It is a
  352.  four-byte parameter with the high byte containing the amplitude.  I use
  353.  SndDoCommand with the noWait flag set to wait for the channel to except
  354.  the command in case the queue is currently full.}
  355.  
  356. VAR
  357.     theCmd:        SndCommand;
  358.  
  359. BEGIN
  360.     WITH theCmd DO BEGIN
  361.         cmd:= noteCmd;
  362.         param1:= duration;
  363.         param2:= note;
  364.     END;
  365.     SendNote:= SndDoCommand(chan, theCmd, kWait);
  366. END;
  367.  
  368. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  369. {$S SoundUnit}
  370. FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
  371.  
  372. {Given a channel, this will place a quietCmd into the channel’s queue.  I
  373.  use SndDoCommand with the noWait flag set to wait for the channel to
  374.  accept the command in the case it is currently full.
  375.  
  376.  BUG NOTE: A sequence of notes and rests will not work unless quietCmds
  377.  are between them.  Rests have to be made quiet before they rest, if that
  378.  makes any more sense.  A noteCmd will loop, causing the sound in progress
  379.  to continue, until a quietCmd is received.}
  380.  
  381. VAR
  382.     theCmd:        SndCommand;
  383.  
  384. BEGIN
  385.     WITH theCmd DO BEGIN
  386.         cmd:= quietCmd;
  387.         param1:= 0;
  388.         param2:= 0;
  389.     END;
  390.     IF immediate THEN
  391.         SendQuiet:= SndDoImmediate(chan, theCmd)
  392.     ELSE
  393.         SendQuiet:= SndDoCommand(chan, theCmd, kWait);
  394. END;
  395.  
  396. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  397. {$S SoundUnit}
  398. FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
  399.  
  400. {Given a channel and duration, this will place the rest into the channel’s
  401.  queue.  Before sending a rest a quietCmd is needed.  Rests don’t work
  402.  unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  403.  with the noWait flag set to wait for the channel to accept the command in
  404.  case it is currently full.}
  405.  
  406. VAR
  407.     theCmd:        SndCommand;
  408.     theErr:        OSErr;
  409.  
  410. BEGIN
  411.     theErr:= SendQuiet(chan, kWait);
  412.     IF theErr = noErr THEN BEGIN
  413.         WITH theCmd DO BEGIN
  414.             cmd:= restCmd;
  415.             param1:= duration;
  416.             param2:= 0;
  417.         END;
  418.         theErr:= SndDoCommand(chan, theCmd, kWait);
  419.     END;
  420.     SendRest:= theErr;
  421. END;
  422.  
  423. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  424. {$S SoundUnit}
  425. PROCEDURE FreeChan(myChan: MyChanPtr);
  426.  
  427. {Test if the channel is free, and if not then call SndDisposeChannel.
  428.  This will release the synthesizer (snth resource) code and the
  429.  required hardware.  If we didn’t do this, no other channels would
  430.  work.  I also test myChan for having a snd resource attached to the
  431.  channel.  If so, then I mark it as purgeable and reset the data to NIL.
  432.  
  433.  BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  434.  a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
  435.  Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  436.  crashing.}
  437.  
  438. VAR
  439.     theErr:         OSErr;
  440.  
  441. BEGIN
  442.     IF myChan^.theChan.userInfo <> kChanFree THEN BEGIN
  443.         theErr:= SendQuiet(SndChannelPtr(myChan), NOT kWait);        {ignore error}
  444.         theErr:= SndDisposeChannel(SndChannelPtr(myChan), NOT kWait);
  445.         myChan^.theChan.userInfo:= kChanFree;
  446.     END;
  447.     IF myChan^.dataHandle <> NIL THEN BEGIN
  448.         HUnlock(myChan^.dataHandle);
  449.         HPurge(myChan^.dataHandle);
  450.         myChan^.dataHandle:= NIL;
  451.     END;
  452. END;
  453.  
  454. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  455. {$S SoundUnit}
  456. PROCEDURE DoSoundComplete;
  457.  
  458. {This routine is called by an application that established a sound to be
  459.  played asynchronously.  This is, in effect, the routine to be used after
  460.  the completion routine has been called.  The application should call this
  461.  once SoundCompletion returns TRUE.  In the case the application is using
  462.  multiple channels, we will only free a channel once it has been marked as
  463.  kChanComplete.  I do not reset the gCalledBack until all the open channels
  464.  are freed.}
  465.  
  466. BEGIN
  467.     IF gChan1^.theChan.userInfo = kChanComplete THEN
  468.         FreeChan(gChan1);
  469.     IF gChan2^.theChan.userInfo = kChanComplete THEN
  470.         FreeChan(gChan2);
  471.     IF gChan3^.theChan.userInfo = kChanComplete THEN
  472.         FreeChan(gChan3);
  473.     IF gChan4^.theChan.userInfo = kChanComplete THEN
  474.         FreeChan(gChan4);
  475.  
  476.     IF (gChan1^.theChan.userInfo = kChanFree)
  477.      & (gChan2^.theChan.userInfo = kChanFree)
  478.      & (gChan3^.theChan.userInfo = kChanFree)
  479.      & (gChan4^.theChan.userInfo = kChanFree) THEN BEGIN
  480.          gCalledBack:= FALSE;
  481.         gChanOpen:= FALSE;                                {no longer making noises}
  482.     END;
  483. END;
  484.  
  485. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  486. {$S SoundUnit}
  487. PROCEDURE FreeAllChans;
  488.  
  489. {This is the routine that will force all channels to be released.  It also
  490.  resets the gCalledBack flag.  This is used by all routines just before
  491.  opening a new channel to force all channels to be disposed.}
  492.  
  493. BEGIN
  494.     FreeChan(gChan1);
  495.     FreeChan(gChan2);
  496.     FreeChan(gChan3);
  497.     FreeChan(gChan4);
  498.     gCalledBack:= FALSE;
  499.     gChanOpen:= FALSE;                                {no longer making noises}
  500. END;
  501.  
  502. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  503. {$S SoundUnit}
  504. PROCEDURE FreeSoundUnit;
  505.  
  506. {This is the final routine to be called by the application when it is has
  507.  finished using this unit.  This will dispose of all the channels and
  508.  memory used by this unit.}
  509.  
  510. BEGIN
  511.     FreeAllChans;
  512.     IF gChan1 <> NIL THEN
  513.         DisposPtr(Ptr(gChan1));
  514.     IF gChan2 <> NIL THEN
  515.         DisposPtr(Ptr(gChan2));
  516.     IF gChan3 <> NIL THEN
  517.         DisposPtr(Ptr(gChan3));
  518.     IF gChan4 <> NIL THEN
  519.         DisposPtr(Ptr(gChan4));
  520.     gCalledBack:= FALSE;
  521. END;
  522.  
  523. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  524. {$S Main}
  525. PROCEDURE DoCallBack(chan: SndChannelPtr; theCmd: SndCommand);
  526.  
  527. {This will be called at interrupt time by the Sound Manager when it
  528.  receives a callBackCmd.  I use the second parameter of the command to hold
  529.  my application’s A5 reference.  I first set up A5 so that I can access my
  530.  globals.  I mark the given channel as being complete and set gCalledBack
  531.  to TRUE.  This lets the application know that the callBackCmd has been
  532.  processed.  The callBackCmd can be used for other purposes, and the first
  533.  parameter of the command could be a flag to a more extensive routine.
  534.  Synchronizing the application with the channel is possible with this
  535.  method.
  536.  
  537.  WARNING: This routine MUST be resident in memory and cannot make a call
  538.  to a non-resident segment.  I put this into the Main segment because of
  539.  this.
  540.  
  541.  BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  542.  'snd '.  A bogus callBackCmd is placed into the queue immediately after
  543.  the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  544.  my callBackProc to be called when I wasn’t expecting it.  I have been
  545.  using the command’s second parameter to contain my A5 address.  If I’m
  546.  given a bogus callBackCmd, it would be really bad to set A5 address to
  547.  this bogus parameter in the command.  I found that the bogus callBackCmd
  548.  contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  549.  that param1 contains the handle’s state bits (results of HGetState).  To
  550.  work with this bug I set my real callBackCmd’s param1 to a specific value
  551.  when I installed it into the queue.  See the SoundComplete routine.  Then
  552.  I test the callBackCmd to make sure I’m dealing with the real one.}
  553.  
  554. VAR
  555.     theA5:         LONGINT;
  556.  
  557. BEGIN
  558.     IF thecmd.param1 = kSoundComplete THEN BEGIN        {if it’s my callBackCmd}
  559.         theA5:= SetA5(theCmd.param2);                     {refer to tech note 208}
  560.         chan^.userInfo:= kChanComplete;                    {this channel is done}
  561.         gCalledBack:= TRUE;
  562.         theA5:= SetA5(theA5);                                {restore original A5}
  563.     END;
  564. END;
  565.  
  566. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  567. {$S SoundUnit}
  568. FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
  569.  
  570. {I use this to install the callBackCmd into the given channel.  I need to
  571.  pass in our A5 along with the command so that the callBack routine can
  572.  access my globals.  I also wait until the channel is ready for another
  573.  command in the case of the channel being full.  Once the Sound Manager
  574.  calls my call back procedure I will dispose of the channel.  So, this is
  575.  the last sound command to be sent to a channel.  I pass to the call back
  576.  A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.}
  577.  
  578. VAR
  579.     theCmd:        SndCommand;
  580.  
  581. BEGIN
  582.     WITH theCmd DO BEGIN
  583.         cmd:= callBackCmd;
  584.         param1:= kSoundComplete;
  585.         param2:= SetCurrentA5;
  586.     END;
  587.     SoundComplete:= SndDoCommand(chan, theCmd, kWait);
  588. END;
  589.  
  590. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  591. {$S SoundUnit}
  592. FUNCTION ChanAvailable(theChan: SndChannelPtr): OSErr;
  593.  
  594. {This routine will test the given channel to see if it will really produce
  595.  sound.  The Sound Manager in System 6.0x will return noErr even if the
  596.  channel isn’t going to work.  In the future the Sound Manager will return
  597.  the proper error.  Until then, I use this routine to determine this for me.
  598.  There can only be a single channel at any time, unless I have the wave
  599.  table synthesizer open.  This will allow four channels.  Channels have
  600.  a pointer to the next channel, and if this is not NIL I suspect the
  601.  given channel will not work.  I test the given channel for being a
  602.  wave type, and if so I need to see if the other channels I’ve got are
  603.  also wave type.  If it doesn’t look like the channels is available, I
  604.  return badChannel.  It is important to set the userInfo field of a channel
  605.  before calling this routine!
  606.  
  607.  BUG NOTE: If an application is not using the Sound Manager and instead
  608.  uses the older Sound Driver, any given channel will fail.  Or if the other
  609.  application does not release is channels, then my channels will not work.
  610.  The most noticeable offender of this is HyperCard.  Friendly applications
  611.  will dispose of their channels at suspend/resume times or ASAP.}
  612.  
  613.     FUNCTION IsMyChan(chan: SndChannelPtr): BOOLEAN;
  614.     BEGIN
  615.         IsMyChan:= ((chan = SndChannelPtr(gChan1))         {is it one of ours?}
  616.                     | (chan = SndChannelPtr(gChan2))
  617.                     | (chan = SndChannelPtr(gChan3))
  618.                     | (chan = SndChannelPtr(gChan4)));
  619.     END;
  620.  
  621.     FUNCTION CompatibleChan(waveChan: SndChannelPtr): BOOLEAN;
  622.     BEGIN
  623.         CompatibleChan:= (waveChan^.userInfo = waveTableSynth)    {wave or..}
  624.                             | (waveChan^.userInfo = kChanFree);            {free chan}
  625.     END;
  626.  
  627. BEGIN
  628.     ChanAvailable:= noErr;
  629.     IF theChan^.nextChan <> NIL THEN BEGIN                            {looks bad}
  630.         ChanAvailable:= badChannel;                                    {prepare to fail}
  631.         IF theChan^.userInfo = waveTableSynth THEN BEGIN        {last attempt}
  632.             IF IsMyChan(theChan^.nextChan)
  633.              & CompatibleChan(SndChannelPtr(gChan1))
  634.              & CompatibleChan(SndChannelPtr(gChan2))
  635.              & CompatibleChan(SndChannelPtr(gChan3))
  636.              & CompatibleChan(SndChannelPtr(gChan4))
  637.                 THEN
  638.                     ChanAvailable:= noErr;                                {got lucky}
  639.         END;
  640.     END;
  641. END;
  642.  
  643. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  644. {$S SoundUnit}
  645. FUNCTION SndDataAvailable(sndHandle: Handle): OSErr;
  646.  
  647. {Given a resource handle, this will attempt to load it into memory.  If the
  648.  data is not available, then return an error.}
  649.  
  650. BEGIN
  651.     SndDataAvailable:= noErr;
  652.     IF sndHandle <> NIL THEN BEGIN
  653.         LoadResource(sndHandle);
  654.         IF sndHandle^ = NIL THEN
  655.             SndDataAvailable:= nilHandleErr;            {master pointer is NIL}
  656.     END ELSE
  657.         SndDataAvailable:= nilHandleErr;                {user passed a NIL handle}
  658. END;
  659.  
  660. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  661. {$S SoundUnit}
  662. FUNCTION HoldSnd(sndHandle: Handle): OSErr;
  663.  
  664. {This is used to put the given sound resource into memory and hold it there.
  665.  I also use a MoveHHi to keep the heap from being fragmented.  If this
  666.  fails, then I return an error.  I dereference the handle and check if the
  667.  master pointer is NIL.  This would mean the data could not be loaded.}
  668.  
  669. BEGIN
  670.     IF sndHandle <> NIL THEN BEGIN
  671.         LoadResource(sndHandle);
  672.         IF sndHandle^ = NIL THEN
  673.             HoldSnd:= nilHandleErr                        {master pointer is NIL}
  674.         ELSE BEGIN
  675.             HoldSnd:= noErr;
  676.             MoveHHi(sndHandle);
  677.             HLock(sndHandle);
  678.         END;
  679.     END ELSE
  680.         HoldSnd:= nilHandleErr;                            {user passed a NIL handle}
  681. END;
  682.  
  683. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  684. {$S SoundUnit}
  685. FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
  686.  
  687. {This routine will return the 'snth' resource ID specified by the sound.
  688.  I use this to determine if the given sound will work with _SndPlay.
  689.  This routine does not require the data to be in memory when called.  It
  690.  also doesn’t lock it down while looking for the information.  I will
  691.  mark the resource as being purgeable in the case the resource attributes
  692.  has it’s non-purgeable bit set.}
  693.  
  694. VAR
  695.     soundPtr:    Ptr;
  696.     theErr:        OSErr;
  697.  
  698. BEGIN
  699.     GetSynthInfo.synthID:= kNoSynth;
  700.     GetSynthInfo.initOption:= 0;
  701.     theErr:= SndDataAvailable(sndHandle);
  702.     IF theErr = noErr THEN BEGIN
  703.         soundPtr:= sndHandle^;
  704.         IF Snd1HdrPtr(soundPtr)^.format = firstSoundFormat THEN BEGIN
  705.             IF Snd1HdrPtr(soundPtr)^.numSynths <> kNoSynth THEN BEGIN
  706.                 soundPtr:= Ptr(ORD4(soundPtr) + SizeOf(Snd1Header));
  707.                 GetSynthInfo.synthID:= SynthInfoPtr(soundPtr)^.synthID;
  708.                 GetSynthInfo.initOption:= SynthInfoPtr(soundPtr)^.initOption;
  709.             END;
  710.         END ELSE BEGIN {snd is a format 2 for HyperCard}
  711.             GetSynthInfo.synthID:= sampledSynth;
  712.             GetSynthInfo.initOption:= 0;        {no options currently supported}
  713.         END;
  714.     HPurge(sndHandle);
  715.     END;
  716. END;
  717.  
  718. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  719. {$S SoundUnit}
  720. FUNCTION GetSndDataOffset(sndHandle: Handle;
  721.                                     VAR dataType, waveLength: INTEGER): LONGINT;
  722.  
  723. {This routine will cruise through the given snd resource.  It will locate
  724.  the sound data, if any, and return its type and offset into the resource.
  725.  I prefer to return an offset instead of a pointer because I don’t want
  726.  to have the data locked in memory.  If I return an offset, the caller
  727.  can decide when and if it wants the resource locked down to access the
  728.  sound data.  The first step in finding this data is to determine if I’m
  729.  looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  730.  require me to find the number of snths specified and then to skip over
  731.  each one including the init option.  Once this is done, I have a pointer
  732.  to the number of commands in the snd.  When I’ve found the first one, I
  733.  examine it to find out if it is a sound data command.  Being it’s a sound
  734.  resource, the command will also have its dataPointerFlag set.  Once I’ve
  735.  found a command I’m looking for I return its type and offset, then get out
  736.  of the REPEAT block.  OTHERWISE I go on to the next command.  All of this
  737.  makes it possible to get the sound data for use as an instrument sound.
  738.  Typically this will be a sampled sound.
  739.  
  740.  WARNING: Do not send this routine a NIL handle.}
  741.  
  742. VAR
  743.     synths,
  744.     howManyCmds:        INTEGER;
  745.     cruisePtr:            Ptr;
  746.  
  747. BEGIN
  748.     GetSndDataOffset:= 0;
  749.     dataType:= kNoSynth;
  750.     waveLength:= 0;
  751.     cruisePtr:= sndHandle^;
  752.     IF cruisePtr <> NIL THEN BEGIN
  753.         IF Snd1HdrPtr(cruisePtr)^.format = firstSoundFormat THEN BEGIN
  754.             synths:= Snd1HdrPtr(cruisePtr)^.numSynths;
  755.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd1Header));
  756.             cruisePtr:= Ptr(ORD4(cruisePtr) + (SizeOf(SynthInfo) * synths));
  757.         END ELSE
  758.             cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd2Header));
  759.         howManyCmds:= IntPtr(cruisePtr)^;    {pointing at number of cmds}
  760.         cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(howManyCmds));
  761.  
  762.                                     {cruisePtr is now at the first sound command}
  763.         REPEAT                    {cruise all commands and find a soundCmd or bufferCmd}
  764.             CASE SndCmdPtr(cruisePtr)^.cmd OF
  765.  
  766.             (soundCmd + dataPointerFlag), (bufferCmd + dataPointerFlag):
  767.                 BEGIN
  768.                     dataType:= sampledSynth;
  769.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  770.                     howManyCmds:= 0;                    {done, get out of loop}
  771.                 END;
  772.  
  773.             (waveTableCmd + dataPointerFlag):
  774.                 BEGIN
  775.                     dataType:= waveTableSynth;
  776.                     waveLength:= SndCmdPtr(cruisePtr)^.param1;
  777.                     GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
  778.                     howManyCmds:= 0;                    {done, get out of loop}
  779.                 END;
  780.  
  781.             OTHERWISE                                    {catch any other type of cmd}
  782.                 BEGIN
  783.                     cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(sndCommand));
  784.                     howManyCmds:= howManyCmds - 1;
  785.                 END;
  786.  
  787.             END;
  788.         UNTIL howManyCmds < 1;                        {done with all the commands}
  789.     END;
  790. END;
  791.  
  792. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  793. {$S SoundUnit}
  794. FUNCTION InstallSampleSnd(myChan: MyChanPtr; sndHandle: Handle): OSErr;
  795.  
  796. {Given a channel and sampled sound resource, this routine will install the
  797.  sound into the channel for use as an instrument.  This allows an
  798.  application to send noteCmds to the channel and play a melody.  If I sent
  799.  a bufferCmd instead of the soundCmd, the Sound Manager would play the
  800.  sampled sound.  This is basically what _SndPlay would do with a format 2
  801.  snd.  I insure that I am using only the proper buffer format having the
  802.  standard encode option.  If I were to support compressed sounds, I would
  803.  have to call the MACE synthesizers to expand the buffer before I can use
  804.  it as an instrument.  If I don’t get a sampled sound of standard encoding
  805.  I’ll return a bad format error.  I use _SndDoImmediate to get the sound
  806.  installed because I don’t want this command to be queued.}
  807.  
  808. VAR
  809.     theCmd:             sndCommand;
  810.     dataPtr:            MySndHeaderPtr;
  811.     dataOffset:        LONGINT;
  812.     sndDataType,
  813.     ignore:            INTEGER;
  814.     theErr:            OSErr;
  815.  
  816. BEGIN
  817.     theErr:= HoldSnd(sndHandle);
  818.     IF theErr = noErr THEN BEGIN
  819.         dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  820.         dataPtr:= MySndHeaderPtr(ORD4(sndHandle^) + dataOffset);
  821.         IF (sndDataType = sampledSynth) & (stdSH = dataPtr^.encode) THEN BEGIN
  822.             WITH theCmd DO BEGIN
  823.                 cmd:= soundCmd;
  824.                 param1:= 0;
  825.                 param2:= ORD4(dataPtr);
  826.             END;
  827.             myChan^.dataHandle:= sndHandle;
  828.             theErr:= SndDoImmediate(SndChannelPtr(myChan), theCmd);
  829.         END ELSE BEGIN
  830.             theErr:= badFormat;                            {return a bad format error}
  831.             HUnlock(sndHandle);                            {and free up the resource}
  832.             HPurge(sndHandle);
  833.         END;
  834.     END;
  835.     InstallSampleSnd:= theErr;
  836. END;
  837.  
  838. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  839. {$S SoundUnit}
  840. FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
  841.                                                         sndInstrument: Handle): OSErr;
  842.  
  843. {This routine will create a sampled sound channel using the INIT option
  844.  given.  Typically this will be 0.  In any case with System 6.0x this
  845.  option is ignored by the sampled sound synthesizer.  The given sound
  846.  resource will be installed into the channel for use as an instrument.
  847.  
  848.  WARNING: If the application does not want an instrument sound, then the
  849.  sndInstrument handle MUST be passed in as NIL.
  850.  
  851.  BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  852.  a Memory Manager error when allocating its internal buffer.  There is a
  853.  call to NewPtr(1316) and if a NIL is returned, the Sound Manager will
  854.  write randomly to low memory.  This can occur when calling _SysBeep under
  855.  low memory conditions.  Also, this pointer is allocated into the
  856.  application’s heap instead of the system’s.}
  857.  
  858. VAR
  859.     theErr:        OSErr;
  860.  
  861. BEGIN
  862.     FreeAllChans;
  863.     theErr:= SndNewChannel(SndChannelPtr(gChan1), sampledSynth,
  864.                                                                         init, @DoCallBack);
  865.     IF theErr = noErr THEN BEGIN
  866.         gChan1^.theChan.userInfo:= sampledSynth;
  867.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  868.         IF (theErr = noErr) & (sndInstrument <> NIL) THEN
  869.             theErr:= InstallSampleSnd(gChan1, sndInstrument);
  870.     END;
  871.     IF theErr <> noErr THEN
  872.         FreeAllChans
  873.     ELSE
  874.         gChanOpen:= TRUE;                                        {got an open channel}
  875.     sampleChan:= SndChannelPtr(gChan1);
  876.     GetSampleChan:= theErr;
  877. END;
  878.  
  879. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  880. {$S SoundUnit}
  881. FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
  882.                             waveLength: INTEGER): OSErr;
  883.  
  884. {Given a channel and pointer to a wave table, this will install the wave
  885.  for use as an instrument into the channel.  If I find the application
  886.  giving me a NIL pointer, I’ll return an error.  I use _SndDoImmediate
  887.  to get the sound installed because I don’t want this to be queued.}
  888.  
  889. VAR
  890.     theCmd:        SndCommand;
  891.  
  892. BEGIN
  893.     IF aWavePtr <> NIL THEN BEGIN
  894.         WITH theCmd DO BEGIN
  895.             cmd:= waveTableCmd;
  896.             param1:= waveLength;
  897.             param2:= ORD4(aWavePtr);
  898.         END;
  899.         InstallWave:= SndDoImmediate(waveChan, theCmd);
  900.     END ELSE
  901.         InstallWave:= memPCErr;                                {Pointer Check failed}
  902. END;
  903.  
  904. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  905. {$S SoundUnit}
  906. FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
  907.                                   waveChan3, waveChan4: SndChannelPtr): OSErr;
  908.  
  909. {This will return four wave table channels with their waves installed.
  910.  When I create a channel I will set the userInfo field marking it with the
  911.  'snth' associated to it.  This must be done before calling ChanAvailable.
  912.  Otherwise that test will fail.  If I cannot obtain all four wave channels
  913.  I will dispose of the ones I did get before returning the error.  This
  914.  routine expects to find four wave table pointers, or it will fail.}
  915.  
  916. VAR
  917.     theErr:        OSErr;
  918.  
  919.     PROCEDURE NewWaveChan(VAR myChan: MyChanPtr; init: INTEGER);
  920.     BEGIN
  921.         theErr:= SndNewChannel(SndChannelPtr(myChan), waveTableSynth,
  922.                                                                         init, @DoCallBack);
  923.         IF theErr = noErr THEN BEGIN
  924.             myChan^.theChan.userInfo:= waveTableSynth;
  925.             theErr:= ChanAvailable(SndChannelPtr(myChan));
  926.         END;
  927.     END;
  928.  
  929. BEGIN
  930.     FreeAllChans;
  931.     NewWaveChan(gChan1, initChan0);
  932.     IF theErr = noErr THEN BEGIN
  933.         NewWaveChan(gChan2, initChan1);
  934.         IF theErr = noErr THEN BEGIN
  935.             NewWaveChan(gChan3, initChan2);
  936.             IF theErr = noErr THEN BEGIN
  937.                 NewWaveChan(gChan4, initChan3);
  938.             END;
  939.         END;
  940.     END;
  941.     IF theErr <> noErr THEN
  942.         FreeAllChans                                            {we didn’t make it}
  943.     ELSE BEGIN
  944.         waveChan1:= SndChannelPtr(gChan1);
  945.         waveChan2:= SndChannelPtr(gChan2);
  946.         waveChan3:= SndChannelPtr(gChan3);
  947.         waveChan4:= SndChannelPtr(gChan4);
  948.         gChanOpen:= TRUE;                                        {now we’re making noise}
  949.     END;
  950.     GetWaveChans:= theErr;
  951. END;
  952.  
  953. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  954. {$S SoundUnit}
  955. FUNCTION GetNoteChan(VAR noteChan: SndChannelPtr; timbre: INTEGER): OSErr;
  956.  
  957. {This will create a channel for the note synthesizer.  When I create a
  958.  channel I will set the userInfo field marking it with the 'snth'
  959.  associated to it.  This must be done before calling ChanAvailable.
  960.  Otherwise that test will fail.  There are no INIT options used by this
  961.  synthesizer, but I will set the timbre to adjust the tone quality.}
  962.  
  963. VAR
  964.     theErr:        OSErr;
  965.  
  966. BEGIN
  967.     FreeAllChans;
  968.     theErr:= SndNewChannel(SndChannelPtr(gChan1), noteSynth,
  969.                                                                 kInitNone, @DoCallBack);
  970.     IF theErr = noErr THEN BEGIN
  971.         gChan1^.theChan.userInfo:= noteSynth;
  972.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  973.         IF theErr = noErr THEN
  974.             theErr:= SetNoteTimbre(SndChannelPtr(gChan1), timbre, NOT kWait);
  975.     END;
  976.     IF theErr <> noErr THEN
  977.         FreeAllChans
  978.     ELSE
  979.         gChanOpen:= TRUE;                                        {now we’re making noise}
  980.     noteChan:= SndChannelPtr(gChan1);
  981.     GetNoteChan:= theErr;
  982. END;
  983.  
  984. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  985. {$S SoundUnit}
  986. FUNCTION GetNoSynthChan(VAR chan: SndChannelPtr): OSErr;
  987.  
  988. {This is the routine to create a channel that isn’t associated with any
  989.  synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  990.  you need to get such a channel.  When I create a channel I will set the
  991.  userInfo field marking it with the 'snth' associated to it.  This must be
  992.  done before calling ChanAvailable.
  993.  
  994.  BUG NOTE: Do not use a channel already associated to a snth with
  995.  _SndPlay.  This causes the Sound Manager to install a second copy of the
  996.  same snth.}
  997.  
  998. VAR
  999.     theErr:        OSErr;
  1000.  
  1001. BEGIN
  1002.     FreeAllChans;
  1003.     theErr:= SndNewChannel(SndChannelPtr(gChan1), kNoSynth,
  1004.                                                                 kInitNone, @DoCallBack);
  1005.     IF theErr = noErr THEN BEGIN
  1006.         gChan1^.theChan.userInfo:= kNoSynth;
  1007.         theErr:= ChanAvailable(SndChannelPtr(gChan1));
  1008.     END;
  1009.     IF theErr <> noErr THEN
  1010.         FreeAllChans
  1011.     ELSE
  1012.         gChanOpen:= TRUE;                                        {now we’re making noise}
  1013.     chan:= SndChannelPtr(gChan1);
  1014.     GetNoSynthChan:= theErr;
  1015. END;
  1016.  
  1017. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1018. {$S SoundUnit}
  1019. FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
  1020.  
  1021. {This routine will use the given channel and snd resource with _SndPlay.
  1022.  This is used to play a sound, which is a series of sound commands commonly
  1023.  referred to as a sequence.  First thing I do is make sure the song fits in
  1024.  memory.  _SndPlay will lock this resource in memory and then pump the snd
  1025.  for all of its worth.  I am calling it asynchronously, and if I was using
  1026.  a snd that contained sound data I wouldn’t mark the snd as being
  1027.  purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1028.  as it returns because it copied all of the commands into the channel.
  1029.  (There’s no data associated with a sequence, just commands.)  _SndPlay
  1030.  will not return until it has done so.  After _SndPlay I need to work
  1031.  around a bug in the noteCmd.  The last thing to do is to send a
  1032.  callBackCmd to signal me that the channel has completed.  If any Sound
  1033.  Manager errors are encountered, I return them to the application.  If the
  1034.  application passed me a NIL snd handle, I’ll return an error.
  1035.  
  1036.  WARNING: Make sure you are using a snd that only has note type commands
  1037.  in it and not something such as a bufferCmd.
  1038.  
  1039.  BUG NOTE: There is problem when the final sound command is a noteCmd.
  1040.  The note will continue to sound, looping forever, until a quietCmd is sent
  1041.  or the channel is disposed of.  To prevent unwanted looping, I send a
  1042.  quietCmd after all notes.  Also read a related bug note when disposing of
  1043.  channels in the routine FreeChan.}
  1044.  
  1045. VAR
  1046.     theErr:            OSErr;
  1047.  
  1048. BEGIN
  1049.     theErr:= SndDataAvailable(sndSong);                {get the data loaded}
  1050.     IF theErr = noErr THEN BEGIN
  1051.         theErr:= SndPlay(chan, sndSong, kSMAsynch);    {pump the sound}
  1052.         HUnlock(sndSong);
  1053.         HPurge(sndSong);
  1054.         IF theErr = noErr THEN BEGIN
  1055.             theErr:= SendQuiet(chan, kWait);            {work around bug}
  1056.             IF theErr = noErr THEN
  1057.                 theErr:= SoundComplete(chan);
  1058.         END;
  1059.     END ELSE
  1060.         PlaySong:= nilHandleErr;                        {snd data was not available}
  1061.     IF theErr <> noErr THEN
  1062.         FreeAllChans;
  1063.     PlaySong:= theErr;
  1064. END;
  1065.  
  1066. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1067. {$S SoundUnit}
  1068. FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
  1069.  
  1070. {This is used to send a syncCmd to a channel and causes the other channels
  1071.  that are being held by a synchCmd to be released.  Of course, this assumes
  1072.  the application has already called SynchChans.  _SndDoImmediate is used
  1073.  to get the command directly to the synthesizer bypassing the queue.
  1074.  
  1075.  BUG NOTE: I’ve found that immediately clearing the channels and starting
  1076.  new ones may cause the channels to startup playing out of synch?  This
  1077.  happens while disposing the wave channels and starting them immediately.}
  1078.  
  1079. VAR
  1080.     theCmd:             sndCommand;
  1081.  
  1082. BEGIN
  1083.     WITH theCmd DO BEGIN
  1084.         cmd:= syncCmd;
  1085.         param1:= 1;
  1086.         param2:= kSyncID;
  1087.     END;
  1088.     ReleaseSynch:= SndDoImmediate(chan, theCmd);
  1089. END;
  1090.  
  1091. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1092. {$S SoundUnit}
  1093. FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
  1094.  
  1095. {This is used to synchronize four wave table channels.  By first sending
  1096.  the synchCmd, I can send a sequence of other commands to the channel and
  1097.  not have the channel attempt to start processing any of them.  That is until
  1098.  another synchCmd is sent causing all of the previous synchCmd’s counter
  1099.  to be decremented.  After getting all the channels in synch and sending
  1100.  the sequence of further commands, then use the ReleaseSynch routine to
  1101.  start all of the channels processing their respective queues.
  1102.  _SndDoImmediate is used to get the command directly to the synthesizer
  1103.  bypassing the queue.}
  1104.  
  1105. VAR
  1106.     theCmd:             SndCommand;
  1107.     theErr:            OSErr;
  1108.  
  1109.     PROCEDURE Synch1Chan(chan: SndChannelPtr; count: INTEGER);
  1110.     BEGIN
  1111.         WITH theCmd DO BEGIN
  1112.             cmd:= syncCmd;
  1113.             param1:= count;
  1114.             param2:= kSyncID;
  1115.         END;
  1116.         theErr:= SndDoImmediate(chan, theCmd);
  1117.     END;
  1118.  
  1119. BEGIN
  1120.     Synch1Chan(chan4, 5);
  1121.     IF theErr = noErr THEN BEGIN
  1122.         Synch1Chan(chan3, 4);
  1123.         IF theErr = noErr THEN BEGIN
  1124.             Synch1Chan(chan2, 3);
  1125.             IF theErr = noErr THEN BEGIN
  1126.                 Synch1Chan(chan1, 2);
  1127.             END;
  1128.         END;
  1129.     END;
  1130.     SynchChans:= theErr;
  1131. END;
  1132.  
  1133. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1134. {$S SoundUnit}
  1135. FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
  1136.                             song1, song2, song3, song4: Handle): OSErr;
  1137.  
  1138. {In order to synchronize channels, the synchCmd is needed.  Once all of the
  1139.  song has been sent into each channel, a final synchCmd is issued to
  1140.  release them.  Don’t send more commands into a channel that it can hold at
  1141.  one time while the channel is in synch mode.}
  1142.  
  1143. VAR
  1144.     theErr:            OSErr;
  1145.  
  1146. BEGIN
  1147.     theErr:= SynchChans(waveChan1, waveChan2, waveChan3, waveChan4);
  1148.     IF theErr = noErr THEN BEGIN
  1149.     theErr:= PlaySong(waveChan1, song1);
  1150.     IF theErr = noErr THEN BEGIN
  1151.         theErr:= PlaySong(waveChan2, song2);
  1152.         IF theErr = noErr THEN BEGIN
  1153.             theErr:= PlaySong(waveChan3, song3);
  1154.             IF theErr = noErr THEN BEGIN
  1155.                 theErr:= PlaySong(waveChan4, song4);
  1156.                 IF theErr = noErr THEN
  1157.                     theErr:= ReleaseSynch(waveChan1);
  1158.                 END;
  1159.             END;
  1160.         END;
  1161.     END;
  1162.     Play4Waves:= theErr;
  1163. END;
  1164.  
  1165. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1166. {$S SoundUnit}
  1167. FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
  1168.  
  1169. {WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I’ve provided
  1170.  this routine because people have asked me how HyperCard performs its PLAY
  1171.  command and why their HyperCard sounds do not sound right using the
  1172.  _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1173.  correctly.  HyperCard is attempting to change the frequency by adjusting
  1174.  the sample rate.  This is NOT the correct approach.  Define the sound
  1175.  buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1176.  If the result from _SndPlay is not what you want, then it is the sample
  1177.  that is incorrect and should be edited.  The sample rate is the rate at
  1178.  which the sound was recorded.  If you didn’t record it, then how do you
  1179.  know what’s the correct rate?  Set the baseNote to the note that was
  1180.  recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1181.  baseNote is middle C.  HyperCard is incorrect in using the sample rate as
  1182.  the frequency of the sound.  Furthermore, using this technique of
  1183.  calculating a new sample rate can introduce errors.  The resulting sample
  1184.  rate will not be the proper pitch.  Also, the sample rate for high pitches
  1185.  will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1186.  problem can happen if the given sample rate was 22k and is to be played
  1187.  back at three octaves higher.  Even 44k samples transposed up a half
  1188.  octave will fail.  Using the soundCmd and noteCmd will not have this
  1189.  problem.
  1190.  
  1191.  Given a sound resource, this routine will play it in the manner that
  1192.  HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1193.  C when the user does not specify a note value in the PLAY command.  I
  1194.  don’t know why.  (What’s middle C when I want to hear speech or the sound
  1195.  of crickets?)  At any rate (pun intended), I get a sampled sound channel.
  1196.  I find the sound data offset in the resource, which has to be locked down
  1197.  at this time.  Once I have the sound data, I get its original sample rate.
  1198.  I have to calculate what a new sample rate would be based on its baseNote.
  1199.  The baseNote is the note at which the sound was recorded.  I’m not sure
  1200.  what this means to crickets, but if this is set to middle C then HyperCard
  1201.  doesn’t attempt to modify the sample rate.  (If you’re wondering how the
  1202.  math works in this routine, buy a book on music theory.  I’m here to
  1203.  provide Mac support.)  Once I’ve adjusted the sample rate, I use the
  1204.  bufferCmd to play it.  Then I restore the sound resource to its original
  1205.  state.  If I didn’t do this it would be possible that the resource was
  1206.  still in memory the next time I use it having the adjusted sample rate.
  1207.  This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1208.  can do this for both a format 1 and 2.
  1209.  
  1210.  BUG NOTE: Do not call SANE while the Sound Manager is running.  Refer to
  1211.  Tech Note #235.}
  1212.  
  1213. VAR
  1214.     theCmd:                 SndCommand;
  1215.     theErr:                OSErr;
  1216.     dataOffset:            LONGINT;
  1217.     sndDataType,
  1218.     power, ignore:        INTEGER;
  1219.     dataPtr:                MySndHeaderPtr;
  1220.     newRate:                Extended;
  1221.     oldRate:                Fixed;
  1222.  
  1223. BEGIN
  1224.     theErr:= HoldSnd(sndHandle);
  1225.     IF theErr = noErr THEN BEGIN
  1226.         theErr:= GetSampleChan(SndChannelPtr(gChan1), kInitNone, NIL);
  1227.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1228.         IF theErr = noErr THEN BEGIN
  1229.  
  1230.             dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
  1231.             dataPtr:= MySndHeaderPtr(ORD4(sndHandle^) + dataOffset);
  1232.             IF (sndDataType = sampledSynth) & (stdSH = dataPtr^.encode) THEN BEGIN
  1233.  
  1234.                 oldRate:= dataPtr^.sampleRate;        {save original sample rate}
  1235.                 IF NOT (dataPtr^.baseNote = kMiddleC) THEN BEGIN
  1236.                     IF dataPtr^.sampleRate < 0 THEN         {large positive number}
  1237.                         newRate:= (Fix2X(BAnd(dataPtr^.sampleRate, maxLongInt))
  1238.                                         + maxLongInt + 1)
  1239.                     ELSE
  1240.                         newRate:= Fix2X(dataPtr^.sampleRate);
  1241.                     newRate:= Fix2X(dataPtr^.sampleRate);
  1242.                     power:= kMiddleC - dataPtr^.baseNote;
  1243.                     dataPtr^.sampleRate:= X2Fix((XpwrI(twelthRootTwo, power)) * newRate);
  1244.                 END;
  1245.                 WITH theCmd DO BEGIN
  1246.                     cmd:= bufferCmd;
  1247.                     param1:= 0;
  1248.                     param2:= ORD4(dataPtr);
  1249.                 END;
  1250.                 theErr:= SndDoImmediate(SndChannelPtr(gChan1), theCmd);
  1251.                 IF theErr = noErr THEN
  1252.                     theErr:= SoundComplete(SndChannelPtr(gChan1));
  1253.                 dataPtr^.sampleRate:= oldRate;            {restore original sample rate}
  1254.             END ELSE BEGIN
  1255.                 theErr:= badFormat;
  1256.                 HUnlock(sndHandle);
  1257.                 HPurge(sndHandle);
  1258.             END;
  1259.         END;
  1260.     END;
  1261.     IF theErr <> noErr THEN
  1262.         FreeAllChans;
  1263.     HyperSndPlay:= theErr;
  1264. END;
  1265.  
  1266. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1267. {$S SoundUnit}
  1268. FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
  1269.  
  1270. {Given a sound resource, this routine will call _SndPlay.  The snd must
  1271.  be either a format 2 or format 1 that contains snth information.
  1272.  Using _SndPlay asynchronously requires us to lock the snd prior to
  1273.  calling the trap.  The reason being is _SndPlay remembers the state of the
  1274.  lock bit using _HGetState and _HSetState.  If the snd is unlocked when
  1275.  it’s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
  1276.  This would be bad when using the sound asynchronously.  If the sound being
  1277.  passed in happens to be a compressed sound created with MACE, it will “do
  1278.  the right thing.”  If MACE isn’t around the Sound Manager will pretend to
  1279.  play a sound but nothing will be heard.
  1280.  
  1281.  BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1282.  a Memory Manager error when allocating its internal buffer.  There is a
  1283.  call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
  1284.  randomly to memory.  Also, the pointer is allocated into the application’s
  1285.  heap instead of the system’s.
  1286.  
  1287.  BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1288.  a bogus callBackCmd into the channel.  This will cause the user’s call
  1289.  back procedure to be called as soon as the sound has completed.  Refer
  1290.  to the DoCallBack routine for details.}
  1291.  
  1292. VAR
  1293.     theErr:            OSErr;
  1294.     synthID:            INTEGER;
  1295.  
  1296. BEGIN
  1297.     theErr:= HoldSnd(sndHandle);                {hold on to the sound}
  1298.     IF theErr = noErr THEN BEGIN
  1299.         theErr:= GetNoSynthChan(SndChannelPtr(gChan1));
  1300.         gChan1^.dataHandle:= sndHandle;        {so FreeAllChans can dispose of data}
  1301.         IF theErr = noErr THEN BEGIN
  1302.             synthID:= GetSynthInfo(sndHandle).synthID;
  1303.             IF synthID <> kNoSynth THEN BEGIN
  1304.                 gChan1^.theChan.userInfo:= synthID;
  1305.                 theErr:= SndPlay(SndChannelPtr(gChan1), sndHandle, kSMAsynch);
  1306.                 IF theErr = noErr THEN
  1307.                     theErr:= SoundComplete(SndChannelPtr(gChan1));
  1308.             END ELSE
  1309.                 theErr:= badFormat;
  1310.         END;
  1311.     END;
  1312.     IF theErr <> noErr THEN
  1313.         FreeAllChans;
  1314.     AsynchSndPlay:= theErr;
  1315. END;
  1316.  
  1317.  
  1318. END. {UNIT}